package aes

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"fmt"
)

/**
AES, Advanced Encryption Standard
分组：128bit，密钥长度：16byte 18byte 24byte
参考资料：https://www.jianshu.com/p/b63095c59361
https://blog.csdn.net/whatday/article/details/98292648
https://www.keylala.cn/aes
*/
/**
CBC模式Aes加密算法
Cipher Block Chaining mode，密码分组链接模式
将明文分组与前一个密文分组进行XOR运算，然后再进行加密。每个分组的加解密都依赖于前一个分组。而第一个分组没有前一个分组，因此需要一个初始化向量（initialization vector）
优点：
1、加密结果与前文相关，有利于提高加密结果的随机性。
2、可并行解密。
缺点
1、无法并行加密。
2、一个分组损坏，如果密文长度不变，则两个分组受影响。
3、一个分组损坏，如果密文长度改变，则后面所有分组受影响。
*/

// AES加密
func EncryptCBCBase64(plaintext string, key string) (ciphertext string, err error) {
	srcData := []byte(plaintext)
	keys := []byte(key)
	block, err := aes.NewCipher(keys)
	if err != nil {
		return
	}
	srcData = pkcs7Padding(srcData, block.BlockSize())
	blockMode := cipher.NewCBCEncrypter(block, keys[:block.BlockSize()])
	dstData := make([]byte, len(srcData))
	blockMode.CryptBlocks(dstData, srcData)
	ciphertext = base64.StdEncoding.EncodeToString(dstData)
	return
}

// AES解密
// ciphertext 密文
// key 密钥
func DecryptCBCBase64(ciphertext, key string) (plaintext string, err error) {
	keys := []byte(key)
	srcData, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		fmt.Println("ciphertext is not base64 encode.", err.Error())
		return
	}
	block, err := aes.NewCipher(keys)
	if err != nil {
		fmt.Println("NewCipher error.", err.Error())
		return
	}

	blockMode := cipher.NewCBCDecrypter(block, keys[:block.BlockSize()])
	dstData := make([]byte, len(srcData))
	blockMode.CryptBlocks(dstData, srcData)
	unPaddingData := pkcs7UnPadding(dstData, block.BlockSize())
	plaintext = string(unPaddingData)
	return
}

/**
使用pkcs7进行分组填充
data 原始数据
blockSize 分组大小，如果最后一个分组长度小于blockSize，则需要对最后一个分组进行填充
如果data的长度为blockSize的整数倍，则需要填充一整个blockSize

在PKCS5Padding中，明确定义Block的大小是8位，
而在PKCS7Padding定义中，对于块的大小是不确定的，可以在1-255之间（块长度超出255的尚待研究），填充值的算法都是一样的：
value=k - (l mod k)  ,K=块大小，l=数据长度，如果l=8, 则需要填充额外的8个byte的8
https://segmentfault.com/a/1190000019793040
*/
func pkcs7Padding(data []byte, blockSize int) []byte {
	paddingSize := blockSize - len(data) % blockSize
	paddingData := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize)	//要填充的数据
	return append(data, paddingData...)
}

func pkcs7UnPadding(data []byte, blockSize int) []byte {
	paddingSize := int(data[len(data) - 1])	//填充字节数
	return data[: (len(data)-paddingSize)]
}